package com.yenlien.traceyun.business.service;

import com.yenlien.traceyun.business.bean.Search;
import com.yenlien.traceyun.business.dao.RecallChainDao;
import com.yenlien.traceyun.business.dao.RecallDao;
import com.yenlien.traceyun.business.entity.Recall;
import com.yenlien.traceyun.business.entity.RecallChain;
import com.yenlien.traceyun.business.entity.TraceScanEntity;
import com.yenlien.traceyun.business.trace.EntityTraceBean;
import com.yenlien.traceyun.business.trace.TraceService;
import com.yenlien.traceyun.common.utils.LoggerUtil;
import com.yenlien.traceyun.webase.config.ApplicationContextConfig;
import com.yenlien.traceyun.webase.dao.BaseJpaDao;
import com.yenlien.traceyun.webase.exception.BusinessException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.*;
import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author: bigwangy@sina.com
 * @create: 2020-12-15
 * @description:
 **/

@Service
public class RecallService {

    @Autowired
    private RecallDao recallDao;

    @Value("${traceyun.app.id}")
    private String appId;

    @Autowired
    private TraceService traceService;

    @Autowired
    private RecallChainDao recallChainDao;

    public void save(Recall recall) {
        recallDao.save(recall);
    }

    public void updateReason(int id, String reason) {
        Recall recall = recallDao.findById(id).get();
        recall.setReason(reason);
        recallDao.save(recall);
    }

    public List<RecallChain> getRecallChain(Recall recall) throws Exception {
        Map<String, List<EntityTraceBean>> branches = getChain(recall);
        List<RecallChain> recallChains = new ArrayList<>();
        for (String branch : branches.keySet()) {
            recallChains.addAll(parseRecallChain(branches.get(branch), recall.getBatchNo()));
        }
        return recallChains;
    }

    public Page<Recall> page(Search search) {
        Pageable pageable = PageRequest.of(search.getPageno() == -1 ? 0 : search.getPageno(), search.getPagesize(), Sort.Direction.DESC, ("recallDate"));
        return recallDao.findAll(create(search), pageable);
    }






    public Specification create(Search search) {
        return new Specification<Recall>() {
            @Override
            public Predicate toPredicate(Root<Recall> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                //构造查询条件
                List<Predicate> list = new ArrayList<>();
                if (!StringUtils.isEmpty(search.getKey())) {
                    list.add(cb.like(root.get("productName"), "%" + search.getKey() + "%"));
                }
                Predicate[] predicate = new Predicate[list.size()];
                return cb.and(list.toArray(predicate));
            }
        };
    }

    /**
     * 获取召回链条
     *
     * @param recall
     */
    private Map<String, List<EntityTraceBean>> getChain(Recall recall) {
        EntityTraceBean entityTraceBean = null;
        EntityTraceBean crossPoint = null;
        Map<String, List<EntityTraceBean>> branchMap = new HashMap<>();
        //找到步骤在链条中的位置
        for (EntityTraceBean bean : traceService.getTraces()) {
            if (recall.getTraceStep().equals(bean.getStepName())) {
                entityTraceBean = bean;
                break;
            }
        }
        if (entityTraceBean == null) {
            throw new BusinessException("500", "未找到步骤：" + recall.getTraceStep());
        }

        //找到步骤在链条中的分支点位置
        for (EntityTraceBean bean : traceService.getTraces()) {
            String[] beancrossPoint = bean.getCorssPront();
            if (beancrossPoint != null && beancrossPoint.length > 0) {
                for (String item : beancrossPoint) {
                    if (item.contains(entityTraceBean.getStepName())) {
                        crossPoint = bean;
                        break;
                    }
                }
            }
            if (crossPoint != null) {
                break;
            }
        }
        //如果在交叉点，且又不在一个分支上
        if (crossPoint != null && !crossPoint.getBranch().equals(entityTraceBean.getBranch())) {
            branchMap.put(crossPoint.getBranch(), getNewChain(crossPoint));
        }
        branchMap.put(entityTraceBean.getBranch(), getNewChain(entityTraceBean));
        return branchMap;

    }

    /**
     * 获取新的链条
     *
     * @param entityTraceBean
     * @return
     */
    private List<EntityTraceBean> getNewChain(EntityTraceBean entityTraceBean) {
        List<EntityTraceBean> branch = traceService.getBranchMap().get(entityTraceBean.getBranch());
        if (branch != null && branch.size() > 0) {
            int flowStartPosition = -1;
            for (int i = 0; i < branch.size(); i++) {
                EntityTraceBean item = branch.get(i);
                if (item.getStepName().equals(entityTraceBean.getStepName())) {
                    flowStartPosition = i;
                    break;
                }
            }
            if (flowStartPosition == -1) {
                throw new BusinessException("500", "未找到分支流程位置");
            }
            List<EntityTraceBean> newbranch = branch.subList(0, flowStartPosition + 1);
            return newbranch;
        } else {
            throw new BusinessException("500", "分支流程为空");
        }
    }

    /**
     * 根据追溯链条修改数据
     *
     * @param newbranch
     */
    private Set<RecallChain> parseRecallChain(List<EntityTraceBean> newbranch, String batch) throws Exception {
        Set<RecallChain> recallChains = new HashSet<>();
        if (newbranch != null && newbranch.size() > 0) {
            int chainSize = newbranch.size();
            List o = sqlObjByBatch(newbranch.get(chainSize - 1), batch);
            EntityTraceBean firstBean = newbranch.get(chainSize - 1);
            recallChains.addAll(fullRecallList(o, firstBean.getStepName(), firstBean.getBranch()));
//            recallChains.add(firstChain);
            if (newbranch.size() > 1) {
//                List<String> tempIds = new ArrayList<>();
//                tempIds.add(getSuperObjValue(o, "id"));
                List tempObj = o;
                for (int i = chainSize - 2; i >= 0; i--) {
                    EntityTraceBean itemBean = newbranch.get(i);
//                    List  list = sqlChildByIds(itemBean, tempIds);
                    List list = sqlChildByObjs(itemBean, tempObj);
                    if (list != null && list.size() > 0) {
                        recallChains.addAll(fullRecallList(list, itemBean.getStepName(), itemBean.getBranch()));
//                        List<TraceScanEntity> scanEntities = list;
//                        tempIds  = scanEntities.stream().map(p->p.getId()).collect(Collectors.toList());
                        tempObj = list;
                    } else {
                        break;
                    }
                }
            }
        }
        return recallChains;
    }

    /**
     * 根据批次查找
     *
     * @param bean
     * @param batch
     * @return
     * @throws Exception
     */
    private List sqlObjByBatch(EntityTraceBean bean, String batch) throws Exception {
        String dao = bean.getClassName().replace("entity", "dao") + "Dao";
        BaseJpaDao baseJpaDao = (BaseJpaDao) ApplicationContextConfig.getBean(Class.forName(dao));
        StringBuilder sql = new StringBuilder();
        sql.append("select * from ").append(bean.getTableName()).append(" where ").append(" batch='").append(batch).append("'");
        LoggerUtil.logInfo("sql:" + sql.toString());
        List info = baseJpaDao.sqlQueryList(sql.toString(), Class.forName(bean.getClassName()), null);
        if (info != null) {
            setObjReall(info);
            baseJpaDao.saveAll(info);
        }
        return info;
    }

    /**
     * 更新recall状态
     */
    public void updateRecall(EntityTraceBean bean, Object o) throws ClassNotFoundException {
        String dao = bean.getClassName().replace("entity", "dao") + "Dao";
        BaseJpaDao baseJpaDao = (BaseJpaDao) ApplicationContextConfig.getBean(Class.forName(dao));
        baseJpaDao.save(o);
    }


    /**
     * 设置对象为召回
     *
     * @param obj
     * @throws Exception
     */
    private void setObjReall(Object obj) throws Exception {
//        if(obj instanceof  List){
//             List list = (List)obj;
//             for(Object o:list){
//                 Field field = o.getClass().getSuperclass().getDeclaredField("recall");
//                 field.setAccessible(true);
//                 field.set(obj,"1");
//             }
//        }else{
//            Field field = obj.getClass().getSuperclass().getDeclaredField("recall");
//            field.setAccessible(true);
//            field.set(obj,"1");
//        }

    }

    /**
     * 根据id
     *
     * @param bean
     * @param id
     * @return
     * @throws Exception
     */
    private List sqlChildById(EntityTraceBean bean, String id) throws Exception {
        String dao = bean.getClassName().replace("entity", "dao") + "Dao";
        BaseJpaDao baseJpaDao = (BaseJpaDao) ApplicationContextConfig.getBean(Class.forName(dao));
        StringBuilder sql = new StringBuilder();
        sql.append("select * from ").append(bean.getTableName()).append(" where ").append(" last_step='").append(id).append("'");
        LoggerUtil.logInfo("sql:" + sql.toString());
        List info = baseJpaDao.sqlQueryList(sql.toString(), Class.forName(bean.getClassName()), null);
        setObjReall(info);
        baseJpaDao.saveAll(info);
        return info;
    }

    /**
     * 根据id
     *
     * @param bean
     * @param ids
     * @return
     * @throws Exception
     */
    private List sqlChildByIds(EntityTraceBean bean, List<String> ids) throws Exception {
        if (ids == null || ids.size() == 0) {
            return null;
        }
        String dao = bean.getClassName().replace("entity", "dao") + "Dao";
        BaseJpaDao baseJpaDao = (BaseJpaDao) ApplicationContextConfig.getBean(Class.forName(dao));
        StringBuilder sql = new StringBuilder();
        sql.append("select * from ").append(bean.getTableName()).append(" where ").append(" last_step in(").append(listToString(ids)).append(")");
        LoggerUtil.logInfo("sql:" + sql.toString());
        List info = baseJpaDao.sqlQueryList(sql.toString(), Class.forName(bean.getClassName()), null);
        setObjReall(info);
        baseJpaDao.saveAll(info);
        return info;
    }


    /**
     * 根据id
     *
     * @param bean
     * @param objects
     * @return
     * @throws Exception
     */
    private List sqlChildByObjs(EntityTraceBean bean, List objects) throws Exception {
        if (objects == null || objects.size() == 0) {
            return null;
        }
        String dao = bean.getClassName().replace("entity", "dao") + "Dao";
        BaseJpaDao baseJpaDao = (BaseJpaDao) ApplicationContextConfig.getBean(Class.forName(dao));
        StringBuilder sql = new StringBuilder();
        Object[] params = null;
        List info = null;
        if (StringUtils.isBlank(bean.getRecallsql())) {
            List<TraceScanEntity> list = objects;
            Set<String> ids = list.stream().map(p -> p.getId()).collect(Collectors.toSet());
            sql.append("select * from ").append(bean.getTableName()).append(" where ").append(" last_step in(").append(listToString(ids)).append(")");
            info = baseJpaDao.sqlQueryList(sql.toString(), Class.forName(bean.getClassName()), null);
        } else {
            sql.append(bean.getRecallsql());

            if (bean.getRecallsqlParams() != null && bean.getRecallsqlParams().length > 0) {
                params = new Object[bean.getRecallsqlParams().length];
                for (int i = 0; i < bean.getRecallsqlParams().length; i++) {
                    List p = getListValue(objects, bean.getRecallsqlParams()[i]);
                    if (p == null) {
                        return null;
                    }
                    params[i] = ((listToString(p)));
                }
            }
            if (bean.isRecallMessageFormat()) {
                info = baseJpaDao.sqlQueryList(MessageFormat.format(sql.toString(), params), Class.forName(bean.getClassName()), null);
            }
        }
        if (info != null) {
            setObjReall(info);
            baseJpaDao.saveAll(info);
        }
        return info;
    }

    /**
     * 获取对象属性
     *
     * @param obj
     * @param fieldName
     * @return
     * @throws Exception
     */
    private String getSuperObjValue(Object obj, String fieldName) throws Exception {
        Field field = obj.getClass().getSuperclass().getDeclaredField(fieldName);
        field.setAccessible(true);
        Object o = field.get(obj);
        if (o != null) {
            return field.get(obj).toString();
        } else {
            return null;
        }
    }

    /**
     * 获取对象属性
     *
     * @param obj
     * @param fieldName
     * @return
     * @throws Exception
     */
    private String getObjValue(Object obj, String fieldName) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        Object o = field.get(obj);
        if (o != null) {
            return field.get(obj).toString();
        } else {
            return null;
        }
    }

    private List<String> getListValue(List list, String fieldName) throws Exception {
        List<String> result = new ArrayList<>();
        if (list != null && list.size() > 0) {
            for (Object o : list) {
                result.add(getObjValue(o, fieldName));
            }
            return result;
        }
        return null;

    }


    /**
     * 给
     *
     * @param o
     */
    private RecallChain fullRecall(Object o, String stepName, String branch) throws Exception {
        RecallChain recallChain = new RecallChain();
        try {
            if (o instanceof RecallData) {
                RecallData recallData = (RecallData) o;
                recallChain.setSpec(recallData.getRecallSpec());
                recallChain.setProductName(recallData.getRecallName());
                recallChain.setNum(recallData.getRecallNum());
            }
        } catch (Exception e) {

        }
        recallChain.setBranch(branch);
        recallChain.setBatch(getObjValue(o, "batch"));
        recallChain.setRelateId(getSuperObjValue(o, "id"));
        recallChain.setTraceStep(stepName);
        return recallChain;
    }

    /**
     * 给
     */
    private Set<RecallChain> fullRecallList(List array, String stepName, String branch) throws Exception {
        Set<RecallChain> chains = new HashSet<>();
        if (array != null && array.size() > 0) {
            for (Object o : array) {
                chains.add(fullRecall(o, stepName, branch));
            }
        }
        return chains;

    }

    @Transactional(rollbackFor = RuntimeException.class)
    public Recall save(Recall recall, List<RecallChain> chains) {
        Recall exist = recallDao.findByTraceStepAndBatchNo(recall.getTraceStep(), recall.getBatchNo());
        if (exist != null) {
            throw new BusinessException("500", "该流程的批次号已经召回过了");
        }
        Recall p = recallDao.save(recall);
        if (chains != null && chains.size() > 0) {
            for (RecallChain chain : chains) {
                chain.setRecallId(p.getId());
            }
            recallChainDao.saveAll(chains);
        }
        return p;
    }


    public Recall addFruitRecall(Recall recall) {
        return recallDao.save(recall);
    }

    public Recall findRecallById(String id) {
        return recallDao.findById(id).orElseThrow(() -> new BusinessException("该召回不存在"));
    }

    public Page<Recall> findRecallPageList(Search search) {
        Pageable pageable = PageRequest.of(search.getPageno() == -1 ? 0 : search.getPageno(), search.getPagesize(), Sort.Direction.DESC, ("recallDate"));
        return recallDao.findAll(pageable);
    }

    public List<Recall> findRecallByCompId(String companyId) {
        return recallDao.findByCompanyId(companyId);
    }





    public String listToString2(Collection<String> arr) {
        StringBuffer stringBuffer = new StringBuffer();
        if (arr != null && arr.size() > 0) {
            for (String item : arr) {
                stringBuffer.append(",").append(item);
            }
            return stringBuffer.substring(1);
        } else {
            return null;
        }
    }

    public String listToString(Collection<String> arr) {
        StringBuffer stringBuffer = new StringBuffer();
        if (arr != null && arr.size() > 0) {
            for (String item : arr) {
                stringBuffer.append(",").append("'").append(item).append("'");
            }
            return stringBuffer.substring(1);
        } else {
            return null;
        }
    }


}
